import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from '../types'
import { parseHeaders } from '../helpers/headers'
import { createError } from '../helpers/error'
import { isURLSameOrigin } from '../helpers/url'
import cookie from '../helpers/cookie'
import { isFormData } from '../helpers/util'


export default function xhr(config: AxiosRequestConfig): AxiosPromise {

  return new Promise((resolve, reject) => {
    // data 默认值 为null  method 默认值为get
    const {
      data = null,
      url,
      method,
      headers = {},
      responseType,
      timeout,
      cancelToken,
      withCredentials,
      xsrfCookieName,
      xsrfHeaderName,
      onDownloadProgress,
      onUploadProgress,
      auth,
      validateStatus
    } = config

    // 发送请求
    const request = new XMLHttpRequest()
    request.open(method!.toUpperCase(), url!, true)

    // 配置 request 对象
    configureRequest()

    // 添加事件
    addEvents()

    // 处理请求 header
    processHeaders()

    // 处理 请求取消
    processCancel()

    request.send(data)

    // Request 相关
    function configureRequest(): void {
      if (responseType) {
        request.responseType = responseType
      }
      if (timeout) {
        request.timeout = timeout
      }
      if (withCredentials) {
        request.withCredentials = withCredentials
      }
    }

    // 事件函数
    function addEvents(): void {
      // success
      request.onreadystatechange = function handleLoad() {
        if (request.readyState !== 4) {
          return
        }

        if (request.status === 0) {
          return
        }
        const responseHeaders = parseHeaders(request.getAllResponseHeaders())
        const responseData = responseType && responseType !== 'text' ? request.response : request.responseText
        const response: AxiosResponse = {
          data: responseData,
          status: request.status,
          statusText: request.statusText,
          headers: responseHeaders,
          config,
          request
        }
        handleResponse(response)
      }

      // error
      request.onerror = function handleError() {
        reject(createError(
          'Network Error',
          config,
          null,
          request
        ))
      }

      // timout
      request.ontimeout = function handleTimeout() {
        reject(createError(
          `Timeout of ${config.timeout} ms exceeded`,
          config,
          'ECONNABORTED',
          request
        ))
      }

      // 下载文件进度监控
      if (onDownloadProgress) {
        request.onprogress = onDownloadProgress
      }

      // 上传文件进度监控
      if (onUploadProgress) {
        request.upload.onprogress = onUploadProgress
      }
    }

    // header
    function processHeaders(): void {
      // 如果是上传 删除 自定义 content-type
      if (isFormData(data)) {
        delete headers['Content-Type']
      }

      // XSRF
      if ((withCredentials || isURLSameOrigin(url!)) && xsrfCookieName) {
        const xsrfValue = cookie.read(xsrfCookieName)
        if (xsrfValue) {
          headers[xsrfHeaderName!] = xsrfValue
        }
      }

      if (auth) {
        headers['Authorization'] = 'Basic ' + btoa(auth.username + ':' + auth.password)
      }
      // 设置 header 必须是 open 以后
      Object.keys(headers).forEach((name) => {
        if (data === null && name.toLowerCase() === 'content-type') {
          delete headers[name]
        } else {
          request.setRequestHeader(name, headers[name])
        }
      })
    }


    // 取消请求
    function processCancel(): void {
      // 取消请求
      if (cancelToken) {
        cancelToken.promise.then(reason => {
          request.abort()
          reject(reason)
        }).catch(
          /* istanbul ignore next */
          () => {
          // do nothing
        })
      }
    }

    // 处理返回 success 状态吗
    function handleResponse(response: AxiosResponse) {
      if (!validateStatus || validateStatus(response.status)) {
        resolve(response)
      } else {
        reject(createError(
          `Request failed with status code ${response.status}`,
          config,
          null,
          request,
          response
        ))
      }
    }
  })
}